package com.tree.redisson;

import org.mybatis.spring.annotation.MapperScan;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Primary;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@MapperScan("com.tree.redisson.mapper")
@SpringBootApplication
public class RedissonApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedissonApplication.class, args);
    }


    @Autowired
    Jedis jedis;
    /**
     * 参考：Redis分布式锁的10个坑https://juejin.cn/post/7178327462869205051?from=search-suggest
     */

    private final static String LOCK_KEY = "xxx";
    private final static long  TIMEOUT = 2*1000*1000;

    /**
     * 方法一 忘记设置过期时间
     * @return
     */
    public boolean distributionLockMethod1() {

        try {
            long result = jedis.setnx(LOCK_KEY, "test");
            if(result == 1){
                //处理业务事情
                doBizs();
                return true;
            }
            return false;
        }finally {
            unlock();
        }
    }



    /**
     * 方法二： 非原子操作（setnx + expire）
     * 因为set not exist 和expire分开， 不是原子操作，所以如果第一个操作成功， 但是服务挂了， 导致永远不会失效
     */
    public void distributionLockMethod2() {

        long result = jedis.setnx(LOCK_KEY, "test");
        if(result == 1){
            jedis.expire(LOCK_KEY, 2);
            //处理业务事情
            doBizs();
        }

    }

    private void unlock() {
        jedis.del(LOCK_KEY);
    }
    private void doBizs() {
        try {
            Random random = new Random();
            Thread.sleep(2000*random.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    /**
     * 方法三：被别的客户端请求覆盖（ setnx + value为过期时间）
     * @return
     */
    public boolean distributionLockMethod3() {

        String expreTime = System.currentTimeMillis()+TIMEOUT+"";
        //如果锁不存在
        if(jedis.setnx(LOCK_KEY, expreTime) == 1){
            return true;
        }
        //如果锁存在， 获取锁过期时间
        String oldExpireTimeStr = jedis.get(LOCK_KEY);

        //如果获取的老的呼气时间小于当前时间， 代表已过期
        if(oldExpireTimeStr != null && Long.parseLong(oldExpireTimeStr) < System.currentTimeMillis()){
            //锁已过期，获取上一个锁的过期时间，并设置现在锁的过期时间（不了解redis的getSet命令的小伙伴，可以去官网看下哈）
            String oldValStr = jedis.getSet(LOCK_KEY, expreTime);
            if(oldValStr != null && oldValStr.equals(oldExpireTimeStr)){
                //考虑多线程并发的情况，只有一个线程的设置值和当前值相同，它才可以加锁
                return true;
            }
        }

        //其他情况均返回加锁失败
        return false;

    }


    /**
     * 方法四：B的锁被A给释放了
     * 假设在这样的并发场景下：A、B两个线程来尝试给Redis的keylockKey加锁，A线程先拿到锁（假如锁超时时间是3秒后过期）。
     * 如果线程A执行的业务逻辑很耗时，超过了3秒还是没有执行完。这时候，Redis会自动释放lockKey锁。
     * 刚好这时，线程B过来了，它就能抢到锁了，开始执行它的业务逻辑，恰好这时，线程A执行完逻辑，去释放锁的时候，它就把B的锁给释放掉了。
     *
     * @return
     */
    public boolean distributionLockMethod4() {
        //线程请求的唯一标记
        String requestId  = UUID.randomUUID().toString();
        long expireTime = System.currentTimeMillis()+TIMEOUT;

        try {
            //set nx px
            String result = jedis.set(LOCK_KEY, requestId, new SetParams().nx().px(expireTime));
            if("1".equals(result)){
                //处理业务事情
                doBizs();
                //加锁成功，处理完业务逻辑返回
                return true;
            }
            return false;
        }finally {
            unlock();
        }

    }

    /**
     * 方法五：基于方法四， 新增判断（确保释放的锁是当前线程）B的锁被A给释放了
     * 假设在这样的并发场景下：A、B两个线程来尝试给Redis的keylockKey加锁，A线程先拿到锁（假如锁超时时间是3秒后过期）。
     * 如果线程A执行的业务逻辑很耗时，超过了3秒还是没有执行完。这时候，Redis会自动释放lockKey锁。
     * 刚好这时，线程B过来了，它就能抢到锁了，开始执行它的业务逻辑，恰好这时，线程A执行完逻辑，去释放锁的时候，它就把B的锁给释放掉了。
     *
     * @return
     */
    public boolean distributionLockMethod5() {
        //线程请求的唯一标记
        String requestId  = UUID.randomUUID().toString();
        long expireTime = System.currentTimeMillis()+TIMEOUT;

        try {
            //set nx px
            String result = jedis.set(LOCK_KEY, requestId, new SetParams().nx().px(expireTime));
            if("1".equals(result)){
                //处理业务事情
                doBizs();
                //加锁成功，处理完业务逻辑返回
                return true;
            }
            return false;
        }finally {
            //确保释放的锁是当前线程
            if(requestId.equals(jedis.get(LOCK_KEY))) {
                unlock();
            }

            String script = "if redis.call('get',KEYS[1]) == ARGV[1] then \n" +
                    "   return redis.call('del',KEYS[1]) \n" +
                    "else\n" +
                    "   return 0\n" +
                    "end;  ";
            jedis.eval(script);
        }

    }

    @Autowired
    private RedissonClient redissonClient;

    public boolean redissionMethod() {

        RLock lock = redissonClient.getLock(LOCK_KEY);
        try {
            lock.tryLock(3, TimeUnit.SECONDS);
            doBizs();
            return true;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return false;
    }




}
